﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace HexEditor_
{
    public struct ConsoleEditParam
    {
        public ConsoleColor clrBackGround;
        public ConsoleColor clrText;
    }

    public class ConsoleEditParameterManager
    {
        public static Dictionary<string, ConsoleEditParam> ParamMap;

        static ConsoleEditParameterManager()
        {
            ParamMap = new Dictionary<string,ConsoleEditParam>();
            AddParameter("select", ConsoleColor.DarkGray, ConsoleColor.Yellow);
            AddParameter("menu", ConsoleColor.DarkGray, ConsoleColor.Yellow);
            AddParameter("char", Console.BackgroundColor, ConsoleColor.White);
            AddParameter("int", Console.BackgroundColor, ConsoleColor.Yellow);
            AddParameter("uint", Console.BackgroundColor, ConsoleColor.DarkYellow);
            AddParameter("float", Console.BackgroundColor, ConsoleColor.Blue);
            AddParameter("double", Console.BackgroundColor, ConsoleColor.DarkBlue);
            AddParameter("short", Console.BackgroundColor, ConsoleColor.Green);
            AddParameter("ushort", Console.BackgroundColor, ConsoleColor.DarkGreen);
            AddParameter("int64", Console.BackgroundColor, ConsoleColor.Red);
            AddParameter("uint64", Console.BackgroundColor, ConsoleColor.DarkRed);
        }
        static void AddParameter(string key, ConsoleColor clrBK,
            ConsoleColor clrText)
        {
            ConsoleEditParam param = new ConsoleEditParam();
            param.clrBackGround = clrBK;
            param.clrText = clrText;
            ParamMap[key] = param;
        }
        public static ConsoleEditParam GetParameterForSelect()
        {
            return GetParameter("select");
        }
        public static ConsoleEditParam GetParameter(string key)
        {
            if (!ParamMap.ContainsKey(key))
            {
                ParamMap[key] = new ConsoleEditParam();
            }
            
            return ParamMap[key];
        }
    }

    public class ConsoleEditView: HexEditView
    {
        public int CommandAreaCharCount { get; set; }
        public Int64 SelStart, SelEnd;

        public override void Initialize() 
        {
            SelStart = SelEnd = -1;

            CommandAreaCharCount = 16;
            //Menu 
            //Content
            //Status
            Console.BufferWidth = Console.WindowWidth;
            Console.BufferHeight = Console.WindowHeight;

            PageLines = Console.BufferHeight - 2;

            if (Editor != null && Editor.GetDoc(0) != null)
            {
                HexDoc doc = Editor.GetDoc(0);
                TotalLines = (doc.GetByteCount() % BytesPerLine) == 0 ?
                    doc.GetByteCount() / BytesPerLine :
                    doc.GetByteCount() / BytesPerLine + 1;
            }
        }

        //Edit的光标位置
        void SetCursorPostion(int x, int y)
        {
            x = x < 0 ? 0 : x >= Console.BufferWidth ? Console.BufferWidth - 1 : x;
            y = y < 0 ? 0 : y >= Console.BufferHeight - 2 ? Console.BufferHeight - 3 : y;
            CursorPosX = x;
            CursorPosY = y;
            Console.SetCursorPosition(x, y + 1);
        }
        public bool InPostionRect(int x, int y)
        {
            return x < 9;
        }
        public bool InBytesRect(int x, int y)
        {
            return x >= 11 && x < BytesPerLine * 3 + 11; 
        }
        public bool InResRect(int x, int y)
        {
            return x > (8 + 3 + BytesPerLine * 3 + 1);
        }
        public int ByteStartCol()
        {
            return 8 + 3;
        }
        //返回被选中的byte，out参数返回的是该byte的行和列相对于窗口
        public Int64 GetByteByCursor(int x, int y, out int sx, out int sy)
        {
            if (InBytesRect(x, y))
            {
                int cs = ByteStartCol();
                int nidx = (x - ByteStartCol() )/3;
                sx = nidx * 3 + ByteStartCol();
                sy = y;
                return BytesPerLine * sy + nidx + CurPos;
            }
            sx = sy = -1;
            return -1;
        }

        public override void OnDocUpdated(Int64 begin = -1, Int64 end = -1)
        {
            Console.Clear();

            //Menu
            ConsoleColor defaultBK = Console.BackgroundColor;
            ConsoleColor defautlFK = Console.ForegroundColor;
            
            ConsoleEditParam param = ConsoleEditParameterManager.GetParameter("menu");
            Console.BackgroundColor = param.clrBackGround;
            Console.ForegroundColor = param.clrText;

            string menustr = "HexEditor+ v1.0       Input :h for help";
            menustr = menustr.PadRight(Console.BufferWidth, ' ');
           
            Console.Write(menustr);

            Console.BackgroundColor = defaultBK;
            Console.ForegroundColor = defautlFK;

            //doc
            HexDoc curDoc = Editor.GetDoc(0);
            if (curDoc != null)
            {
                RedrawDoc(curDoc);
            }

            UpdateStatus();
        }

        public void UpdateStatus()
        {
            //status
            int nLen = Console.BufferWidth - CommandAreaCharCount - 2;

            Console.SetCursorPosition(CommandAreaCharCount + 1, Console.BufferHeight - 1);
            string str = String.Format("Mode: {0}  Cursor: {1}, {2} --- {3}, {4}",
                Program.GetCurrentMode().GetModeName(),
                CursorPosX,
                CursorPosY + CurPos / BytesPerLine,
                CursorPosX,
                CursorPosY);
            
            if (str.Length > nLen)
            {
                str = str.Substring(str.Length - nLen, nLen);
            }
            else
            {
                str = str.PadLeft(nLen, ' ');
            }
            Console.Write(str);
            SetCursorPostion(CursorPosX, CursorPosY);
        }

        public void ShowContent(string str)
        {
            Console.Clear();

            Console.SetCursorPosition(0, 0);
            Console.Write(str);

        }
        public void ShowCommand(string str)
        {
            Console.SetCursorPosition(0, Console.BufferHeight - 1);
            string strshow = str;
            if (str.Length > CommandAreaCharCount)
            {
                strshow = str.Substring(str.Length - CommandAreaCharCount,
                    CommandAreaCharCount);
            }
            else
            {
                strshow = str.PadRight(CommandAreaCharCount, ' ');
            }
            Console.Write(strshow);
            SetCursorPostion(CursorPosX, CursorPosY);
        }

        bool IsSelected(Int64 cur)
        {
            if (SelStart <= SelEnd)
            {
                return cur >= SelStart && cur <= SelEnd;
            }
            else
            {
                return cur >= SelEnd && cur <= SelStart;
            }
        }

        public void DrawLine(HexDoc doc, 
            byte[] DocData, 
            Int64 byteOffset, 
            int byteCount,
            int yPos, 
            ref Int64 cur, 
            Int64 cend,
            ref int firstFormatPos)
        {            
            SetCursorPostion(0, yPos);

            ConsoleColor defaultBK = Console.BackgroundColor;
            ConsoleColor defautlFK = Console.ForegroundColor;

            Console.Write(cur.ToString("X8"));  //Position
            Console.Write(cur.ToString(" | "));

            int curFormatPos = doc.GetFormatMarkPos(cur, firstFormatPos);
            firstFormatPos = curFormatPos;
            FormatMark formatMark = doc.GetFormatMark(curFormatPos);
            ConsoleEditParam viewParam = ConsoleEditParameterManager.GetParameter(formatMark.DataTypeName);
            ConsoleEditParam viewSelParam = ConsoleEditParameterManager.GetParameterForSelect();
            int nextFormatPos = -1;

            Console.BackgroundColor = viewParam.clrBackGround;
            Console.ForegroundColor = viewParam.clrText;

            Int64 it = cur;
            while (it < cend)
            {
                nextFormatPos = doc.GetFormatMarkPos(it, curFormatPos);
                if (nextFormatPos != curFormatPos)
                {
                    curFormatPos = nextFormatPos;
                    formatMark = doc.GetFormatMark(nextFormatPos);
                    viewParam = ConsoleEditParameterManager.GetParameter(formatMark.DataTypeName);

                    Console.BackgroundColor = viewParam.clrBackGround;
                    Console.ForegroundColor = viewParam.clrText;
                }

                if (IsSelected(it))
                {
                    Console.BackgroundColor = viewSelParam.clrBackGround;
                    Console.ForegroundColor = viewSelParam.clrText;

                    Console.Write(DocData[it - byteOffset].ToString("X2"));
                    Console.Write(" ");

                    Console.BackgroundColor = viewParam.clrBackGround;
                    Console.ForegroundColor = viewParam.clrText;
                }
                else
                {
                    Console.Write(DocData[it - byteOffset].ToString("X2"));
                    Console.Write(" ");
                }
                ++it;
            }

            Console.BackgroundColor = defaultBK;
            Console.ForegroundColor = defautlFK;
                
            string str = "";
            if (it < (cur + BytesPerLine))
            {
                str = str.PadRight((int)(cur + BytesPerLine - it) * 3, ' ');
                Console.Write(str);
            }
            Console.Write("| ");

            if (curFormatPos != firstFormatPos)
            {
                curFormatPos = firstFormatPos;
                formatMark = doc.GetFormatMark(curFormatPos);
                viewParam = ConsoleEditParameterManager.GetParameter(formatMark.DataTypeName);
            }
            Console.BackgroundColor = viewParam.clrBackGround;
            Console.ForegroundColor = viewParam.clrText;

            int parsedLen;
            DataTypeManager dtManager = Editor._DataTypeManager;

            it = cur;
            while (it < cend)
            {
                nextFormatPos = doc.GetFormatMarkPos(it, curFormatPos);
                if (!doc.IsItemStart(it, curFormatPos, ref dtManager))
                {
                    ++it;
                    continue;
                }

                if (nextFormatPos != curFormatPos)
                {
                    curFormatPos = nextFormatPos;
                    formatMark = doc.GetFormatMark(nextFormatPos);
                    viewParam = ConsoleEditParameterManager.GetParameter(formatMark.DataTypeName);

                    Console.BackgroundColor = viewParam.clrBackGround;
                    Console.ForegroundColor = viewParam.clrText;
                }

                str = doc.ParseItem(
                    DocData,
                    (int)(it - byteOffset),
                    out parsedLen,
                    curFormatPos,
                    ref dtManager);
                it += (parsedLen);

                Console.Write(str);
                Console.Write(" ");
            }

            Console.BackgroundColor = defaultBK;
            Console.ForegroundColor = defautlFK;

            firstFormatPos = curFormatPos;
            cur = cend;
        }

        public void DrawLine(int yPos)
        {
            int curX = CursorPosX;
            int curY = CursorPosY;

            HexDoc doc = Editor.GetDoc(0);
            //暂时绘制全部可见部分
            Int64 cur = CurPos + BytesPerLine * yPos;
            Int64 lend = cur + BytesPerLine;
            if (lend > doc.GetByteCount())
                lend = doc.GetByteCount();
            if (lend <= cur)
            {
                return;
            }

            Int64 byteOffset = cur;
            int byteCount = (int)(lend - cur);
            byte[] DocData = doc.GetBytes(ref byteOffset, ref byteCount);

            int firstFormat = 0;
            DrawLine(doc, DocData, byteOffset, byteCount, yPos, ref cur, lend, ref firstFormat);

            SetCursorPostion(curX, curY);
        }

        public void RedrawDoc(HexDoc doc)
        {
            //暂时绘制全部可见部分
            Int64 cur = CurPos;
            Int64 cend = CurPos + BytesPerLine * PageLines;
            if (cend > doc.GetByteCount())
                cend = doc.GetByteCount();

            Int64 byteOffset = cur;
            int byteCount = (int)(cend - cur);
            byte[] DocData = doc.GetBytes(ref byteOffset, ref byteCount);

            int curX = CursorPosX;
            int curY = CursorPosY;

            int nLine = 0;
            int formatPos = 0;
            while (cur < cend)
            {
                DrawLine(doc, DocData, byteOffset, byteCount,
                    nLine, ref cur,
                    cur + BytesPerLine > cend ? cend : cur + BytesPerLine,
                    ref formatPos);
                ++nLine;
            }

            SetCursorPostion(curX, curY);
        }

        public void DrawData(Int64 start, Int64 end)
        {
            HexDoc doc = Editor.GetDoc(0);
            if (doc == null)
            {
                return;
            }

            if (start > end)
            {
                Int64 temp = start;
                start = end;
                end = temp;
            }
            else if (start == end)
            {
                return;
            }

            Int64 pageEnd = CurPos + BytesPerLine * PageLines;
            if (end <= CurPos || start >= pageEnd ||
                start >= doc.GetByteCount())
            {
                return;
            }

            if (start < CurPos)
            {
                start = CurPos;
            }

            start = ((start - CurPos)/BytesPerLine) * BytesPerLine + CurPos;
            end = ((end - CurPos) % BytesPerLine == 0) ? end :
                ((end - CurPos)/BytesPerLine + 1) * BytesPerLine + CurPos;

            if (end > pageEnd)
            {
                end = pageEnd;
            }
            if (end > doc.GetByteCount())
            {
                end = doc.GetByteCount();
            }

            if (start >= end)
            {
                return;
            }


            Int64 byteOffset = start;
            int byteCount = (int)(end - start);
            byte[] DocData = doc.GetBytes(ref byteOffset, ref byteCount);

            int curX = CursorPosX;
            int curY = CursorPosY;

            int nLine = ((int)(start - CurPos))/BytesPerLine;
            int formatPos = 0;
            while (start < end)
            {
                DrawLine(doc, DocData, byteOffset, byteCount,
                    nLine, ref start,
                    start + BytesPerLine > end ? end : start + BytesPerLine,
                    ref formatPos);
                ++nLine;
            }

            SetCursorPostion(curX, curY);
        }

        public void PageMoveUp()
        {
            Console.MoveBufferArea(0, 1, 
                Console.BufferWidth, Console.BufferHeight - 3,
                0, 2);
            DrawLine(0);
        }
        public void PageMoveDown()
        {
            Console.MoveBufferArea(0, 2,
                Console.BufferWidth, Console.BufferHeight - 3,
                0, 1);
            DrawLine(Console.BufferHeight - 3);
        }

        public void MoveCursorTo(int x, int y, bool bShift)
        {
            if (y < 0)
            {
                if (CurPos > 0)
                {
                    CurPos -= BytesPerLine;
                    PageMoveUp();
                }
            }
            else if (y > Console.BufferHeight - 3)
            {
                if (CurPos + PageLines < TotalLines - 1)
                {
                    CurPos += BytesPerLine;
                    PageMoveDown();
                }
            }

            x = x < 0 ? 0 : x > Console.BufferWidth - 1 ? Console.BufferWidth - 1 : x;
            y = y < 0 ? 0 : y > Console.BufferHeight - 3 ? Console.BufferHeight - 3 : y;


            if (InBytesRect(x, y))
            {
                int sX, sY;
                Int64 bitPos;
                Int64 pervStart = SelStart;
                Int64 pervEnd = SelEnd;
                bitPos = GetByteByCursor(x, y, out sX, out sY);
                if (bShift)
                {
                    SelEnd = bitPos;
                }
                else
                {
                    SelStart = SelEnd = bitPos;
                }
                ShowSelectBytes(pervStart, pervEnd, SelStart, SelEnd);
            }
            CursorPosX = x;
            CursorPosY = y;
            Console.SetCursorPosition(x, y + 1);
        }

        static void Swap(ref Int64 a1, ref Int64 a2)
        {
            Int64 t = a1;
            a1 = a2;
            a2 = t;
        }
        static void MakeInc(ref Int64 a1, ref Int64 a2)
        {
            if (a1 > a2)
                Swap(ref a1, ref a2);
        }

        public void ShowSelectBytes(Int64 pervStart, Int64 pervEnd,
            Int64 curStart, Int64 curEnd)
        {
            MakeInc(ref pervStart, ref pervEnd);
            MakeInc(ref curStart, ref curEnd);
            ++pervEnd;
            ++curEnd;
            if (pervEnd < curStart || curEnd < pervStart)
            {
                DrawData(pervStart, pervEnd);
                DrawData(curStart, curEnd);
            }
            else
            {
                if (curStart < pervStart)
                {
                    pervStart = curStart;
                }
                if (curEnd > pervEnd)
                {
                    pervEnd = curEnd;
                }
                DrawData(pervStart, pervEnd);
            }
        }

        public void MoveCursorLeft(bool bShift)
        {
            MoveCursorTo(CursorPosX - 1, CursorPosY, bShift);
            UpdateStatus();
        }
        public void MoveCursorRight(bool bShift)
        {
            MoveCursorTo(CursorPosX + 1, CursorPosY, bShift);
            UpdateStatus();
        }
        public void MoveCursorUp(bool bShift)
        {
            MoveCursorTo(CursorPosX, CursorPosY - 1, bShift);
            UpdateStatus();
        }
        public void MoveCursorDown(bool bShift)
        {
            MoveCursorTo(CursorPosX, CursorPosY + 1, bShift);
            UpdateStatus();
        }

        public void ShowDataTypeList(DataTypeManager _dtManager)
        {
            Console.Clear();
            Console.SetCursorPosition(0, 0);
            int num = _dtManager._AvailableTypeList.Count;
            string str;
            int dtLen = 16;
            str = "Data type";
            str = str.PadRight(dtLen, ' ');
            Console.Write(str);
            Console.Write("Example\n\n");
            ConsoleColor clrBK = Console.BackgroundColor;
            ConsoleColor clrText = Console.ForegroundColor;
            for (int i = 0; i < num; ++i)
            {
                str = _dtManager._AvailableTypeList[i];
                str = str.PadRight(dtLen, ' ');
                Console.Write(str);

                ConsoleEditParam param = ConsoleEditParameterManager.GetParameter(_dtManager._AvailableTypeList[i]);
                Console.BackgroundColor = param.clrBackGround;
                Console.ForegroundColor = param.clrText;
                Console.Write(_dtManager.GetDataType(_dtManager._AvailableTypeList[i]).GetExample());

                Console.BackgroundColor = clrBK;
                Console.ForegroundColor = clrText;
                Console.Write('\n');
            }
        }
    }
}
